package ee.rumba.iPhoneShop.dictionary;

import java.io.*;
import java.util.Vector;
import java.util.Collections;
import java.util.HashMap;
import java.util.Iterator;

public class iDictionary {
  private boolean sorted;

  Vector<iWord> iWordVector;

  public String toString() {
    return "iDictionary with " + iWordVector.size() + " words";
  }

  public iDictionary() {
    iWordVector = new Vector<iWord>(1000, 1000);
    sorted = false;
  }

  public void addiWord(iWord iword) {
    iWordVector.add(iword);
    sorted = false;
  }

  public void writeTextDump(File textDumpFile) throws Exception {
    FileOutputStream fout = new FileOutputStream(textDumpFile);
    fout.write("UTF8Word\tX-Boolean\tWeight\n".getBytes());
    int i = 0;
    for (iWord iw : iWordVector) {
      fout.write(iw.toString().getBytes("UTF-8"));
      fout.write("\n".getBytes());
    }
    fout.flush();
    fout.close();
    System.out.println("Text dump written into file " + textDumpFile.getAbsolutePath());
  }

  public void writeStemsDat(File stemsFile) throws Exception {
    if (!sorted)
      throw new IllegalArgumentException("UnigramsIdx can be only created after execute of writeUnigramsDat");

    HashMap<String, String> stemBase = new HashMap<String, String>(1000, 1000);

    for (iWord anIWordVector : iWordVector) {
      String first3Chars = anIWordVector.get3FirstAnsiChars();
      String first2Chars = first3Chars.substring(0, 2) + " ";
      if (!stemBase.containsKey(first3Chars) && validSteam(first3Chars)) stemBase.put(first3Chars, first3Chars);
      if (!stemBase.containsKey(first2Chars) && validSteam(first2Chars)) stemBase.put(first2Chars, first3Chars);
    }


    Iterator<String> keySet = stemBase.keySet().iterator();
    Vector<String> finalStemBase = new Vector<String>(stemBase.size());
    while (keySet.hasNext()) {
      finalStemBase.add(keySet.next());
    }
    Collections.sort(finalStemBase);

    // this point we have all 2-3 character stem's in Vector finalStemBase
    // and in fast HashMap stemBase;

    HashMap<Character, char[]> iKeyboard = getiKeyboard();
    HashMap<String, Vector<String>> mutants = new HashMap<String, Vector<String>>(1000, 1000);
    for (String s : finalStemBase) {
      mutants.put(s, genMutants(s, stemBase, iKeyboard));
    }

    // Here we have mutants HM and finalStemBase vector for sorted things.
    // Calculations

    int dataBegin = finalStemBase.size() * 7;
    int dataPos = dataBegin;
    ByteArrayOutputStream byteBuffer = new ByteArrayOutputStream();

    byteBuffer.write(iWord.createIntBytes(dataBegin));
    for (String key : finalStemBase) {
      byteBuffer.write(key.getBytes());
      byteBuffer.write(iWord.createIntBytes(dataPos));
      Object o = mutants.get(key);
      Vector<String> v = null;
      if (o instanceof Vector) {
        v = (Vector<String>) o;
      }
      int totalLen = 0;
      for (String aV : v) {
        totalLen += aV.length() + 1;
      }
      dataPos += totalLen + 1; // extra %00
    }

    for (String key : finalStemBase) {
      Object o = mutants.get(key);
      Vector<String> v = (Vector<String>) o;
      int totalLen = 0;
      for (String aV : v) {
        byteBuffer.write(aV.getBytes());
        byteBuffer.write((byte) 0);
      }
      byteBuffer.write((byte) 0);
    }
    FileOutputStream fout = new FileOutputStream(stemsFile);
    fout.write(byteBuffer.toByteArray());
    fout.flush();
    fout.close();
    System.out.println("Stems written into " + stemsFile.getAbsolutePath());
  }

  public void writeUnigramsIdx(File idxFile) throws Exception {
    if (!sorted)
      throw new IllegalArgumentException("UnigramsIdx can be only created after execute of writeUnigramsDat");

    ByteArrayOutputStream byteBuffer = new ByteArrayOutputStream();
    iWord currWord = null;
    for (iWord anIWordVector : iWordVector) {
      if (currWord == null || !currWord.compareIndexChars(anIWordVector)) {
        currWord = anIWordVector;
        byteBuffer.write(currWord.getIndexBytes());
      }
    }
    FileOutputStream fout = new FileOutputStream(idxFile);
    fout.write(byteBuffer.toByteArray());
    fout.flush();
    fout.close();
    System.out.println("Index written into " + idxFile.getAbsolutePath());
  }

  public void writeUnigramsDat(File datFile) throws Exception {
    System.out.println("Start Unigrams.dat write");
    if (!sorted) {
      System.out.println("Starts sorting " + iWordVector.size() + " words");
      sort();
      System.out.println("Sorting done");
    }
    ByteArrayOutputStream byteBuffer = new ByteArrayOutputStream();
    byteBuffer.write(iWord.createIntBytes(iWordVector.size())); // file header

    for (iWord anIWordVector : iWordVector) {
      byteBuffer.write(anIWordVector.createBinaryWord(byteBuffer.size()));
    }

    FileOutputStream fout = new FileOutputStream(datFile);
    fout.write(byteBuffer.toByteArray());
    fout.flush();
    fout.close();
    System.out.println("Unigrams written into " + datFile.getAbsolutePath());
  }

  private void sort() {
    Collections.sort(iWordVector);
    sorted = true;
  }

  /*
  4 bytes header = word count
  words {
    4 bytes infoBits
    4 bytes uppercaseBits OPTIONAL
    X word bytes (unicode word len = WORD_LEN)
    0x00 ending byte
  }
  infoBits
  {WL&0x7} {WL=WORD_LEN }
  32 31 30 29 28 27 26 25
  
  24 23 22 21 20 19 18 17

  16 15 14 13 12 11 10 09


                       |- UPPER_CASE BITS
  08 07 06 05 04 03 02 01

   */
  public static iDictionary parseUnigramsDat(File dictionaryDatFile) throws Exception {
    iDictionary idict = new iDictionary();
    FileInputStream fin = new FileInputStream(dictionaryDatFile);

    byte[] bytes = new byte[(int) dictionaryDatFile.length()];
    int read = fin.read(bytes);

    if (read != (int) dictionaryDatFile.length()) throw new IOException("Unable to read full file into memory");

    int pos = 0;
    int wordCount = iWord.readInt(bytes, pos);
    pos += 4;
    int parsedWordCount = 0;
    while (pos < read) {
      int sizeBits = iWord.readShort(bytes, pos);
      pos += 2;
      int weightBits = (int) bytes[pos++] & 0xFF;
      int extraBits = (int) bytes[pos++] & 0xFF;
      int wordUnicodeLen = iWord.getUnicodeLen(sizeBits);
      boolean unknownBit = false;
      int upperCaseBits = 0;
      if ((extraBits & iWord.CASE_SENSITIVE) != 0) {
        upperCaseBits = iWord.readInt(bytes, pos);
        pos += 4;
      }

      if ((extraBits & iWord.UNKNOWN_BIT) != 0) unknownBit = true;

      if ((extraBits >> 2) != 0) {
        throw new IllegalArgumentException("Broken dictionary file, extraBits byte have more than 2 bits info");
      }

      byte[] wordBuffer = new byte[255];
      int wordBufferIdx = 0;
      int wordLenCounter = 0;
      while (bytes[pos] != 0 && wordLenCounter < wordBuffer.length) {
        wordBuffer[wordBufferIdx] = bytes[pos];
        // if unicode char, then do not increase wordLenCounte;
        if (((bytes[pos] & 0xFF) != 0xC2) && ((bytes[pos] & 0xFF) != 0xC3) && ((bytes[pos] & 0xFF) != 0xC4) && ((bytes[pos] & 0xFF) != 0xC5)) {
          wordLenCounter++;
        }
        wordBufferIdx++;
        pos++;
      }
      if (wordUnicodeLen != 0 && wordUnicodeLen != wordLenCounter) {
        throw new IllegalArgumentException("Broken dictionary file: @ position " + pos);
      }
      pos++; // terminator 0 byte

      String stringValue = new String(wordBuffer, 0, wordBufferIdx, "UTF-8");
      if (upperCaseBits != 0) {
        stringValue = makeUpperCase(stringValue, upperCaseBits);
      }

      int calcSizeBits = iWord.createWordLenghtBits(stringValue);
      if (calcSizeBits != sizeBits) {
        System.out.println(stringValue);
        System.out.println(getBits(calcSizeBits, 16));
        System.out.println(getBits(sizeBits, 16));
        System.out.println();
      }
      int calcWordBits = 0;
      if (upperCaseBits != 0) calcWordBits |= iWord.CASE_SENSITIVE;
      calcWordBits |= 2;  // only if full uniq word.
      // keep 0 if similar word is present.

      idict.addiWord(new iWord(stringValue, weightBits, unknownBit));
      parsedWordCount++;
    }
    if (parsedWordCount != wordCount) throw new IllegalArgumentException("Broken dictionary file");
    return idict;

  }

  private static String makeUpperCase(String s, int bits) {
    StringBuffer sb = new StringBuffer(s.length());
    for (int i = 0; i < s.length(); i++) {
      String x = s.substring(i, i + 1);
      if ((bits & (1 << i)) != 0) {
        sb.append(x.toUpperCase());
      } else {
        sb.append(x);
      }
    }
    return sb.toString();
  }

  private boolean validSteam(String s) {
    // no numbers valid
    // not 2 whitespace valid
    byte[] bytes = s.getBytes();
    int whitespaces = 0;
    int numbers = 0;
    for (byte aByte : bytes) {
      if (aByte >= '0' && aByte <= '9') numbers++;
      if (aByte == ' ') whitespaces++;
    }
    return (whitespaces < 2 && numbers == 0);
  }

  public Vector<String> genMutants(String org, HashMap<String, String> stemBase, HashMap<Character, char[]> iKeyboard) {
    Vector<String> v = new Vector<String>(10, 10);
    char[] A1 = iKeyboard.get(org.charAt(0));
    char[] A2 = iKeyboard.get(org.charAt(1));
    char[] A3 = iKeyboard.get(org.charAt(2));

    // STUPED Apple bug, lets implement is same way :)
    if (A1 == null) {
      char c = org.charAt(0);
      A1 = new char[]{c, c, c, c, c, c, c, c};
    }
    if (A2 == null) {
      char c = org.charAt(1);
      A2 = new char[]{c, c, c, c, c, c, c, c};
    }
    if (A3 == null) {
      char c = org.charAt(2);
      A3 = new char[]{c, c, c, c, c, c, c, c};
    }

    for (char aA1 : A1) {
      for (char aA2 : A2) {
        for (char aA3 : A3) {
          char[] b = new char[3];
          b[0] = aA1;
          b[1] = aA2;
          b[2] = aA3;
          String s = new String(b);
          if (stemBase.containsKey(s)) {
            // mutant must have at least 1 matching character.
            if (b[0] == org.charAt(0) || b[1] == org.charAt(1) || b[2] == org.charAt(2)) {
              v.add(s.trim());
            }
          }
        }
      }
    }
    return v;
  }

  private HashMap<Character, char[]> getiKeyboard() {
    HashMap<Character, char[]> iKeyboard = new HashMap<Character, char[]>(27);
    iKeyboard.put('q', new char[]{'q', 'w', 'a'});
    iKeyboard.put('w', new char[]{'w', 'e', 'q', 'a', 's'});
    iKeyboard.put('e', new char[]{'e', 'r', 'w', 's', 'd'});
    iKeyboard.put('r', new char[]{'r', 't', 'e', 'd', 'f'});
    iKeyboard.put('t', new char[]{'t', 'y', 'r', 'f', 'g'});
    iKeyboard.put('y', new char[]{'y', 'u', 't', 'g', 'h'});
    iKeyboard.put('u', new char[]{'u', 'i', 'y', 'h', 'j'});
    iKeyboard.put('i', new char[]{'i', 'o', 'u', 'j', 'k'});
    iKeyboard.put('o', new char[]{'o', 'p', 'i', 'k', 'l'});
    iKeyboard.put('p', new char[]{'p', 'o', 'l'});

    //iKeyboard.put('a', new char[]{'a', 's', 'q', 'w', 'z'});
    // Apple have changed stems pattern from 1.1.1 version. They have missed thet Q may be as A

    iKeyboard.put('a', new char[]{'a', 's', 'w', 'z'});

    iKeyboard.put('s', new char[]{'s', 'd', 'a', 'z', 'w', 'e', 'x'});
    iKeyboard.put('d', new char[]{'d', 'f', 's', 'x', 'e', 'r', 'z', 'c'});
    iKeyboard.put('f', new char[]{'f', 'g', 'd', 'c', 'r', 't', 'x', 'v'});
    iKeyboard.put('g', new char[]{'g', 'h', 'f', 'v', 't', 'y', 'c', 'b'});
    iKeyboard.put('h', new char[]{'h', 'j', 'g', 'b', 'y', 'u', 'v', 'n'});
    iKeyboard.put('j', new char[]{'j', 'k', 'h', 'n', 'u', 'i', 'b', 'm'});
    iKeyboard.put('k', new char[]{'k', 'l', 'j', 'm', 'i', 'o', 'n'});
    iKeyboard.put('l', new char[]{'l', 'k', 'o', 'p', 'm'});

    iKeyboard.put('z', new char[]{'z', 'x', 's', 'a', 'd'});
    iKeyboard.put('x', new char[]{'x', 'c', 'z', 'd', 's', 'f'});
    iKeyboard.put('c', new char[]{'c', 'v', 'x', 'f', 'd', 'g'});
    iKeyboard.put('v', new char[]{'v', 'b', 'c', 'g', 'f', 'h'});
    iKeyboard.put('b', new char[]{'b', 'n', 'v', 'h', 'g', 'j'});
    iKeyboard.put('n', new char[]{'n', 'm', 'b', 'j', 'h', 'k'});
    iKeyboard.put('m', new char[]{'m', 'n', 'k', 'j', 'l'});

    iKeyboard.put(' ', new char[]{' '});

    return iKeyboard;
  }

  private static String getBits(long i, int len) {
    String s = Long.toBinaryString(i);
    for (int x = s.length(); x < len; x++) s = "0" + s;
    return s;
  }

  private static String get32Bits(long i) {
    char[] buf = new char[32];
    int charPos = 32;
    int radix = 1 << 1;
    long mask = 1;
    do {
      buf[--charPos] = ((i & mask) == 0) ? '0' : '1';
      i >>>= 1;
    } while (charPos != 0);
    return new String(buf);

  }
}
